import filecmp
import os

import table_ast
import table_lexer
import table_parser

class CsTableConfig:
    def __init__(self, keyType, keyValue):
        self.keyType, self.keyValue = keyType, keyValue

def to_cs_files(indir, outdir, cfg):
    def check_replace_file(filename):
        if not os.path.exists(filename[:-1]) or \
           not filecmp.cmp(filename, filename[:-1], False):
            os.replace(filename, filename[:-1])
        else:
            os.remove(filename)

    def parse_ast(infile):
        lexer = table_lexer.TableLexer()
        lexer.parse(infile)
        parser = table_parser.TableParser()
        return parser.parse(lexer)

    def parse_blanks(infile):
        blanks = []
        with open(infile, 'r', encoding='utf8') as fo:
            for i, line in enumerate(fo.readlines()):
                if line.isspace():
                    blanks.append(i + 1)
        return blanks

    def generate(indir, outdir, filename):
        filepart = os.path.splitext(filename)[0]
        outpathpart = os.path.join(outdir, filepart)
        infile = os.path.abspath(os.path.join(indir, filename))
        print('cs files %s ...' % infile)
        root, blanks = parse_ast(infile), parse_blanks(infile)
        outfile, prefix = outpathpart+'.cs~', \
            'using System.Collections.Generic;\n\n'
        GeneratorCS(blanks, cfg).Generate(root, outfile, prefix)
        check_replace_file(outfile)

    if os.path.exists(indir):
        if not os.path.exists(outdir):
            os.mkdir(outdir)
        for filename in os.listdir(indir):
            generate(indir, outdir, filename)

class GeneratorBase:
    @classmethod
    def _to_cs_type(cls, node):
        if node.container is None:
            if isinstance(node.parts[0], table_ast.NsIdList):
                return cls._to_cs_ns(node.parts[0])
            if isinstance(node.parts[0], table_ast.DateTime):
                return cls.__to_cs_datetime(node.parts[0])
            if isinstance(node.parts[0], table_ast.Time):
                return cls.__to_cs_time(node.parts[0])
            return cls.__to_cs_base_type(node.parts[0].value)
        if node.container == 'Sequence':
            return 'List<' + \
                cls._to_cs_type(node.parts[0]) + '>'
        if node.container == 'Associative':
            return 'Dictionary<' + \
                cls._to_cs_type(node.parts[0]) + ', ' + \
                cls._to_cs_type(node.parts[1]) + '>'
        if node.container == 'Unique':
            return 'HashSet<' + \
                cls._to_cs_type(node.parts[0]) + '>'

    @staticmethod
    def _to_cs_ns(node):
        return '.'.join(ns.value for ns in node.idList)

    @staticmethod
    def __to_cs_datetime(node):
        return 'long'

    @staticmethod
    def __to_cs_time(node):
        return 'int'

    @staticmethod
    def __to_cs_base_type(strType):
        allTypes = {'int8' : 'sbyte', 'uint8' : 'byte',
                    'int16': 'short', 'uint16': 'ushort',
                    'int32': 'int',   'uint32': 'uint',
                    'int64': 'long',  'uint64': 'ulong',
                    'bytes': 'byte[]'}
        if strType in allTypes:
            return allTypes[strType]
        else:
            return strType

class GeneratorCS(GeneratorBase):
    __inherit_base = 'BinaryHelper.ISerializable'

    def __init__(self, blanks, cfg):
        self.blanks, self.i = blanks, 0
        self.lineno = 0
        self.cfg = cfg

    def Generate(self, root, outfile, prefix = ''):
        with open(outfile, 'w') as self.fo:
            self.fo.write(prefix)
            for entity in root.externalDeclarations:
                self.__generateentity(entity, 0)

    def __generateentity(self, entity, level):
        if isinstance(entity, table_ast.TableDefinition):
            return self.__generatetableblock(entity, level)
        if isinstance(entity, table_ast.StructDefinition):
            return self.__generatestructblock(entity, level)
        if isinstance(entity, table_ast.EnumDefinition):
            return self.__generateenumblock(entity, level)

        if isinstance(entity, table_ast.TableMemberVarDeclaration) or \
           isinstance(entity, table_ast.StructMemberVarDeclaration):
            return self.__generatestructmember(entity, level)
        if isinstance(entity, table_ast.EnumDeclaration):
            return self.__generateenummember(entity, level)

        if isinstance(entity, table_ast.Preprocessor):
            return self.__writeline2file(level,
                '//' + entity.text.value.rstrip(), entity.text.lineno)

        if isinstance(entity, table_ast.Comment):
            return self.__writeline2file(level,
                entity.text.value.rstrip(), entity.text.lineno,
                canInline = True, strSpacer = '  ')

    def __generatetableblock(self, entity, level):
        self.__writeline2file(level, 'public class ' + entity.name.value + ' : ' +
            self.__inherit_base, entity.name.lineno)
        self.__generateblockmember(entity, level)

    def __generatestructblock(self, entity, level):
        inheritBase = entity.inheritBase
        self.__writeline2file(level, 'public class ' + entity.name.value + ' : ' +
            (self._to_cs_ns(inheritBase) if inheritBase else self.__inherit_base),
            entity.name.lineno)
        self.__generateblockmember(entity, level)

    def __generateenumblock(self, entity, level):
        self.__writeline2file(level, 'public enum ' + entity.name.value,
            entity.name.lineno)
        self.__generateblockmember(entity, level)

    def __generateblockmember(self, entity, level):
        self.__writeline2file(level, '{')
        for member in entity.declarationList.declarationList:
            self.__generateentity(member, level + 1)
        if isinstance(entity, table_ast.TableDefinition):
            self.__generatetablefuncs(entity, level + 1)
        elif isinstance(entity, table_ast.StructDefinition):
            self.__generatestructfuncs(entity, level + 1)
        self.__writeline2file(level, '};')

    def __generatestructmember(self, member, level):
        self.__writeline2file(level,
            'public ' + self._to_cs_type(member.memType) + ' ' +
            self.__MemberVarVarDeclaratorList2Str(member.memVarList) + ';',
            member.memVarList.varDeclaratorList[0].lineno)

    def __generateenummember(self, member, level):
        self.__writeline2file(level, member.memName.value +
            self.__enumValue2formatStr(member.memValue) + ',',
            member.memName.lineno, canInline = True, strSpacer = ' ')

    def __writeline2file(self, level, linedata, lineno = -1, **kwargs):
        while len(self.blanks) > self.i and self.blanks[self.i] < lineno:
            self.fo.write('\n')
            self.i += 1
        if kwargs and kwargs['canInline'] and self.lineno == lineno:
            self.fo.seek(self.fo.tell() - len(os.linesep))
            self.fo.write(kwargs['strSpacer'])
        elif level != 0:
            self.fo.write('\t' * level)
        self.fo.write(linedata)
        self.fo.write('\n')
        if lineno != -1:
            self.lineno = lineno

    @staticmethod
    def __MemberVarVarDeclaratorList2Str(node):
        return ', '.join(var.value for var in node.varDeclaratorList)

    @classmethod
    def __enumValue2formatStr(cls, node):
        return (' = ' + (cls._to_cs_ns(node) if
            isinstance(node, table_ast.NsIdList) else node)) if node else ''

    def __generatetablefuncs(self, entity, level):
        name, keyName, tblName = entity.name.value, None, None
        if entity.metaInfoList:
            for metaInfo in entity.metaInfoList.metaInfoList:
                if metaInfo[0].value == 'key':
                    keyName = metaInfo[1].value
                elif metaInfo[0].value == 'tblname':
                    tblName = metaInfo[1].value

        allMembers = []
        for member in entity.declarationList.declarationList:
            if isinstance(member, table_ast.TableMemberVarDeclaration):
                for memVar in member.memVarList.varDeclaratorList:
                    allMembers.append((member.memType, memVar))

        self.fo.write('\n')

        self.__writeline2file(level, 'public static string GetTableName() {')
        self.__writeline2file(level + 1, 'return "%s";' % (tblName or name))
        self.__writeline2file(level, '}')

        self.__writeline2file(level, 'public static string GetTableKeyName() {')
        self.__writeline2file(level + 1, 'return %s;' % ('"%s"' % keyName if keyName else 'string.Empty'))
        self.__writeline2file(level, '}')

        self.__writeline2file(level, 'public %s GetTableKeyValue() {' % self.cfg.keyType)
        self.__writeline2file(level + 1, 'return (%s)%s;' % (self.cfg.keyType, keyName or self.cfg.keyValue))
        self.__writeline2file(level, '}')

        self.__writeline2file(level, 'public virtual void LoadFromBuffer(NetBuffer buffer) {')
        self.__generateloadfromstream(allMembers, level + 1)
        self.__writeline2file(level, '}')

        self.__writeline2file(level, 'public virtual void SaveToBuffer(NetBuffer buffer) {')
        self.__generatesavetostream(allMembers, level + 1)
        self.__writeline2file(level, '}')

    def __generatestructfuncs(self, entity, level):
        allMembers = []
        for member in entity.declarationList.declarationList:
            if isinstance(member, table_ast.StructMemberVarDeclaration):
                for memVar in member.memVarList.varDeclaratorList:
                    allMembers.append((member.memType, memVar))

        strModifier = 'override' if entity.inheritBase else 'virtual'
        self.fo.write('\n')

        self.__writeline2file(level, 'public %s void LoadFromBuffer(NetBuffer buffer) {' % strModifier)
        if entity.inheritBase:
            self.__writeline2file(level + 1, 'base.LoadFromBuffer(buffer);')
        self.__generateloadfromstream(allMembers, level + 1)
        self.__writeline2file(level, '}')

        self.__writeline2file(level, 'public %s void SaveToBuffer(NetBuffer buffer) {' % strModifier)
        if entity.inheritBase:
            self.__writeline2file(level + 1, 'base.SaveToBuffer(buffer);')
        self.__generatesavetostream(allMembers, level + 1)
        self.__writeline2file(level, '}')

    def __generateloadfromstream(self, members, level):
        for member in members:
            if member[0].container is None:
                if isinstance(member[0].parts[0], table_ast.NsIdList):
                    self.__writeline2file(level, 'BinaryHelper.LoadFromBuffer(out %s, buffer);' % member[1].value)
                else:
                    self.__writeline2file(level, 'BinaryHelper.ReadFromBuffer(out %s, buffer);' % member[1].value)
            elif member[0].container == 'Sequence':
                if isinstance(member[0].parts[0].parts[0], table_ast.NsIdList):
                    self.__writeline2file(level, 'BinaryHelper.BlockSequenceReadFromBuffer(out %s, buffer);' % member[1].value)
                else:
                    self.__writeline2file(level, 'BinaryHelper.SequenceReadFromBuffer(out %s, buffer);' % member[1].value)
            elif member[0].container == 'Associative':
                if isinstance(member[0].parts[1].parts[0], table_ast.NsIdList):
                    self.__writeline2file(level, 'BinaryHelper.BlockAssociativeReadFromBuffer(out %s, buffer);' % member[1].value)
                else:
                    self.__writeline2file(level, 'BinaryHelper.AssociativeReadFromBuffer(out %s, buffer);' % member[1].value)
            elif member[0].container == 'Unique':
                self.__writeline2file(level, 'BinaryHelper.UniqueReadFromBuffer(out %s, buffer);' % member[1].value)

    def __generatesavetostream(self, members, level):
        for member in members:
            if member[0].container is None:
                if isinstance(member[0].parts[0], table_ast.NsIdList):
                    self.__writeline2file(level, 'BinaryHelper.SaveToBuffer(%s, buffer);' % member[1].value)
                else:
                    self.__writeline2file(level, 'BinaryHelper.WriteToBuffer(%s, buffer);' % member[1].value)
            elif member[0].container == 'Sequence':
                if isinstance(member[0].parts[0].parts[0], table_ast.NsIdList):
                    self.__writeline2file(level, 'BinaryHelper.BlockSequenceWriteToBuffer(%s, buffer);' % member[1].value)
                else:
                    self.__writeline2file(level, 'BinaryHelper.SequenceWriteToBuffer(%s, buffer);' % member[1].value)
            elif member[0].container == 'Associative':
                if isinstance(member[0].parts[1].parts[0], table_ast.NsIdList):
                    self.__writeline2file(level, 'BinaryHelper.BlockAssociativeWriteToBuffer(%s, buffer);' % member[1].value)
                else:
                    self.__writeline2file(level, 'BinaryHelper.AssociativeWriteToBuffer(%s, buffer);' % member[1].value)
            elif member[0].container == 'Unique':
                self.__writeline2file(level, 'BinaryHelper.UniqueWriteToBuffer(%s, buffer);' % member[1].value)
